use std::collections::HashMap;
use std::os::unix::prelude::PermissionsExt;

use actix_web::web;
use chrono::{Local, Duration};
use diesel::prelude::*;
use md5::{Md5, Digest};
use crate::response;
use crate::model;

use crate::{models::{self, MalObj, MalObjType}, ip_block::{self, IpBlock}, action, model::recovery_list_model::RecoveryList};

type DbError = Box<dyn std::error::Error + Send + Sync>;

/// Run query using Diesel to find user by uid and return it.
pub fn find_alert_by_id(
    alert_id: String,
    conn: &SqliteConnection,
) -> Result<Option<models::Alert>, DbError> {
    use crate::schema::alerts::dsl::*;

    let alert = alerts
        .filter(id.eq(alert_id))
        .first::<models::Alert>(conn)
        .optional()?;

    Ok(alert)
}

pub fn find_alert_by_page(
    page: i64,
    size: i64,
    source_op: Option<&String>,
    conn: &SqliteConnection,
) -> Result<(Option<Vec<models::Alert>>, i64), DbError> {
    use crate::schema::alerts::dsl::*;

    let offset = (page - 1) * size;

    match source_op {
        Some(src) => {
            let alert_vec = alerts
                .filter(solved.eq(false))
                .filter(source.eq(src))
                .order(time.desc())
                .limit(size)
                .offset(offset)
                .load::<models::Alert>(conn)
                .optional()?;
    
            let count = alerts.filter(solved.eq(false)).filter(source.eq(src)).count().get_result::<i64>(conn)?;
        
            Ok((alert_vec, count))
        },
        None => {
            let alert_vec = alerts
                .filter(solved.eq(false))
                .order(time.desc())
                .limit(size)
                .offset(offset)
                .load::<models::Alert>(conn)
                .optional()?;
    
            let count = alerts.filter(solved.eq(false)).count().get_result::<i64>(conn)?;
        
            Ok((alert_vec, count))
        },
    }
    
}

/// Run query using Diesel to insert a new database row and return the result.
pub fn insert_new_alert(
    // prevent collision with `name` column imported inside the function
    new_alert: models::Alert,
    conn: &SqliteConnection,
) -> Result<models::Alert, DbError> {
    // It is common when using Diesel with Actix Web to import schema-related
    // modules inside a function's scope (rather than the normal module's scope)
    // to prevent import collisions and namespace pollution.
    use crate::schema::alerts::dsl::*;

    diesel::insert_into(alerts).values(&new_alert).execute(conn)?;

    Ok(new_alert)
}


/// Run query using Diesel to insert a new database row and return the result.
pub fn solve_alert(
    // prevent collision with `name` column imported inside the function
    alert_id: String,
    ip_block: web::Data<ip_block::Iptables>,
    conn: &SqliteConnection,
) -> Result<bool, DbError> {
    // It is common when using Diesel with Actix Web to import schema-related
    // modules inside a function's scope (rather than the normal module's scope)
    // to prevent import collisions and namespace pollution.
    // use crate::schema::alerts::dsl::*;

    let alert_op = find_alert_by_id(alert_id, conn)?;
    match alert_op {
        Some(alert) => {

            let mut hasher = Md5::new();
            hasher.update(alert.mal_obj_type.as_bytes());
            hasher.update(alert.mal_obj_key.as_bytes());
            let result = hasher.finalize();
            let id = format!("{:x}", result);
            let create_time = Local::now().timestamp_millis();
            let expire_time = Local::now().checked_add_signed(Duration::days(365)).unwrap().timestamp_millis();
            let mal_obj_key = alert.mal_obj_key.to_string();
            let mal_obj_type = alert.mal_obj_type.to_string();
            let recovery_type = alert.mal_obj_type.to_string();
            let mut recovery_key = alert.mal_obj_key.to_string();
            let mut recovery_value = "".to_string();

            // 阻断IP
            if alert.mal_obj_type == "IP" {
                // 执行IP阻断
                ip_block.add_timeout(&alert.mal_obj_key, 1000 * 60 * 10);
            }
            // 暂停进程
            if alert.mal_obj_type == "Process" {
                let pid = alert.mal_obj_key.parse::<i32>().unwrap();
                response::process_suspend::suspend(pid);
            }
            // 隔离文件
            if alert.mal_obj_type == "File" {
                // TODO 备份文件
                //&alert.mal_obj_key;
                // TODO 删除文件
                //&alert.mal_obj_key;
                recovery_key = "data/isolate/".to_string() + id.as_str();
                let perms = std::fs::metadata(alert.mal_obj_key.as_str()).unwrap().permissions();
                let mode = perms.mode();
                recovery_value = serde_json::to_string(&model::recovery_list_model::RecoveryValue  {
                    mode: Some(mode)
                }).unwrap();
                response::file_isolate::isolate(alert.mal_obj_key.as_str(), &recovery_key);
            }

            // 记录处置记录
            let _result = action::recovery_list_action::insert(RecoveryList {
                id,
                mal_obj_key,
                mal_obj_type,
                recovery_type,
                recovery_key,
                recovery_value,
                create_time,
                expire_time,
                source: "user".to_string()
            }, &conn);

        },
        None => (),
    }

    Ok(true)
}